﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Net.Sockets;
using System.Windows.Forms;
using System.Diagnostics;

namespace GShare1._0.Server
{
    public delegate void MainThreadInvoker(Delegate MI);
    public delegate void MessageBoxInvoker(string msg);
    public delegate void ClientListUpdateDelegate(List<Client> clientList);
    public class Client
    {
        public Socket ClientSocket;
        public byte[] Buffer;
        public MainThreadInvoker MainInvoker;
        private MessageBoxInvoker _invoker;
        public Download Download;
        public Socket DownloadSocket;
        public Socket UpdateSocket;
        public AutoResetEvent AutoEvent;
        public ClientInfo ClientInfo;
        private Server server;
        private MessageRelayDelegate relayInvoker;

        public Client(Socket socket, MessageBoxInvoker invoker,ClientInfo CI,Server server,MessageRelayDelegate relayInvoker,MainThreadInvoker mainInvoker)
        {
            AutoEvent = new AutoResetEvent(false);
            ClientSocket = socket;
            MainInvoker = mainInvoker;
            Buffer = new byte[ClientSocket.ReceiveBufferSize];
            _invoker = invoker;
            Debug.WriteLine("Created Client");
            ClientInfo = CI;
            this.server = server;
            this.relayInvoker = relayInvoker;
        }



        internal void ReceiveCallback(IAsyncResult ar)
        {
            try
            {
                int dataRead = ClientSocket.EndReceive(ar);

                if (dataRead == 0)
                {
                    Disconnect();
                    return;
                }
                //resize buffer to amount received
                Array.Resize(ref Buffer, dataRead);

                //write buffer to string
                string msg = Encoding.ASCII.GetString(Buffer);

                string[] msgDetails = msg.Split(',');


                if (!string.IsNullOrEmpty(msg))
                {
                    switch (msgDetails[0])
                    {
                        case "SENDFILE":
                            //MessageBox.Show("Download Starting");
                            break;
                        case "TEXT":
                            TextReader();
                            break;
                        case "PRIVATE_MESSAGE":
                            PrivateMessageRelay();
                            break;

                        default:
                            //Send Error Code to the Client
                            MessageSender.SendText("COMMANDERROR", ClientSocket);
                            break;
                    }
                }

                //Loop the Receive

                Buffer = new byte[ClientSocket.ReceiveBufferSize];

                try
                {
                    ClientSocket.BeginReceive(Buffer, 0, ClientSocket.ReceiveBufferSize, SocketFlags.None, new AsyncCallback(ReceiveCallback), null);
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message);
                }
            }
            catch (Exception ex)
            {
                //No Propper way to handle this problem
                //http://stackoverflow.com/questions/4662553/how-to-abort-sockets-beginreceive
                //"To cancel a pending call to the BeginConnect() method, close the Socket. 
                //When the Close() method is called while an asynchronous operation is in progress, 
                //the callback provided to the BeginConnect() method is called. A subsequent
                //call to the EndConnect(IAsyncResult) method will throw an ObjectDisposedException to
                //indicate that the operation has been cancelled."
                if (ex.GetType() != typeof(ObjectDisposedException))
                {
                    MessageBox.Show(ex.Message);
                }
            }
        }

        private void PrivateMessageRelay()
        {
            byte[] buffer = new byte[ClientSocket.ReceiveBufferSize];
            int received = ClientSocket.Receive(buffer);

            Array.Resize(ref buffer, received);
            string msg = Encoding.ASCII.GetString(buffer);

            string[] splitMsg = msg.Split(',');

            foreach (Client client in server.ClientList.ToArray())
            {
                if (client.ClientInfo.UserName == splitMsg[0])
                {
                    MessageSender.SendText("*" + ClientInfo.UserName + ": " + splitMsg[1], client.ClientSocket);
                }
            }
        }

        //Called When Download Finished
        void download_Finish(object sender, EventArgs e)
        {
            Download = null;
        }

        void downloadForm_FormClosing(object sender, FormClosingEventArgs e)
        {
 
        }

        internal void Disconnect()
        {
            _invoker.BeginInvoke(ClientInfo.UserName +" Disconnected", null, null);
            relayInvoker.BeginInvoke(ClientInfo.UserName + " Disconnected", null, null);
            ClientSocket.Shutdown(SocketShutdown.Both);
            //_clientSocket.Dispose();
            ClientSocket.Close();
            server.ClientList.Remove(this);
            server.ServerRoom.ClientList.Remove(this);
            server.ConnectedClientInfoList.Remove(this.ClientInfo);
            server.UpdateListInvoker.BeginInvoke(server.ClientList, null, null);
            if (server.IsOnline) server.SendClientInfo();
            
        }

        internal void TextReader()
        {
            byte[] buffer = new byte[ClientSocket.ReceiveBufferSize];
            int received = ClientSocket.Receive(buffer);

            Array.Resize(ref buffer, received);
            string msg = Encoding.ASCII.GetString(buffer);

            if (!string.IsNullOrEmpty(msg))
            {
                _invoker.BeginInvoke(ClientInfo.UserName + ": " + msg, null, null);
                relayInvoker.BeginInvoke(ClientInfo.UserName + ": " + msg, null, null);
            }
        }

        //starts the BeginListUpdate Process
        internal void BeginListUpdate(Socket updateSocket)
        {
            if (UpdateSocket == null)
            {
                UpdateSocket = updateSocket;
            }
            //check if the connection is already active
            if (UpdateSocket.Connected)
            {

                UpdateSocket.BeginSend(server.ClientInfoBuffer.ToArray(), 0, server.ClientInfoBuffer.Count, SocketFlags.None, new AsyncCallback(ListUpdateCallback), null);

                
            }
        }

        private void ListUpdateCallback(IAsyncResult ar)
        {
            UpdateSocket.EndSend(ar);
            UpdateSocket.Close();
            UpdateSocket = null;
            
        }

    }
}
